package ij.umasuo.gui.working;

import ij.IJ;
import ij.ImagePlus;
import ij.measure.ResultsTable;
import ij.plugin.filter.Analyzer;
import ij.plugin.filter.GaussianBlur;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;
import ij.process.ShortProcessor;
import ij.umasuo.util.MyDialogs;
import ij.umasuo.util.MyFunctions;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JPanel;
/**
 * COPY FROM THE QUICKPALM,use to show the reconstructed image 
 * @author liuquan
 * @date 2013/08/06
 * @version 1.0
 *
 */
public class ReconstructionViewer extends JPanel implements ComponentListener,ActionListener{
	
	private static final long serialVersionUID = 1L;
	private static ReconstructionViewer instance = null;
	public ImagePlus imp;
	public ImageProcessor ip;
	private MyImageCanvas canvas = null;
	public MyDialogs settings = new MyDialogs();
	public ResultsTable table;
	public MyFunctions functions;
	
	public GaussianBlur gblur = new GaussianBlur();
	
	public int position = 0;
	public int max=0;
	public int min=9999999;
	public double maxZ=10;
	public double minZ=-10;
	
	public	double [] s;
	public double [] x;
	public double [] y;
	public double [] z;
	public double [] f;
	public double [] cl;
	public int nframes = 0;
	
	//the components we show
	private JPanel northPanel = new JPanel();
	private JPanel southPanel = new JPanel();
	private JPanel centerPanel = new JPanel();
	JButton btnSettings = new JButton("Settings");
	
	/**
	 *  Class constructer
	 */
	private ReconstructionViewer(){
		this.addComponentListener(this);
		this.setLayout(null);
		canvas = new MyImageCanvas();
		centerPanel.setBounds(20, 30, 410, 210);
		centerPanel.setLayout(new BorderLayout(0, 0));
		this.add(centerPanel,BorderLayout.CENTER);
		centerPanel.add(canvas,BorderLayout.CENTER);
//		centerPanel.setBackground(Color.red);
		
		northPanel.setBounds(0, 0, 450, 30);
		this.add(northPanel,BorderLayout.NORTH);
		southPanel.setBounds(0, 250, 450, 50);
		this.add(southPanel,BorderLayout.SOUTH);
		southPanel.setLayout(null);
		
		btnSettings.addActionListener(this);
		btnSettings.setBounds(21, 11, 89, 23);
		southPanel.add(btnSettings);
		
		loadSettings();
		int new_width=settings.viewer_owidth;
		int new_height=settings.viewer_oheight;
		
		// create the processor
		if (settings.view_mode == settings.view_modes[3]) // 2D particle intensity (8-bit)
			ip=new ByteProcessor(new_width, new_height);
		else if (settings.view_mode == settings.view_modes[1] || settings.view_mode == settings.view_modes[2]) // 2D histogram or 2D particle intensity (16-bit)
			ip=new ShortProcessor(new_width, new_height);
		else // color
			ip=new ColorProcessor(new_width, new_height);
		
		// create the image
		imp = new ImagePlus("", ip);
	}
	
	public static ReconstructionViewer getInstance(){
		if(instance == null){
			instance = new ReconstructionViewer();
		}
		return instance;
	}
	public void reset(){
		table = Analyzer.getResultsTable();
//		functions = func;
		
		position = 0;
		max=0;
		min=9999999;
		maxZ=10;
		minZ=-10;
	}
	/** Class constructer used on the offline rendering mode.
	 * @param title name for the rendering window
	 * @param dg dialog manager
	 * @param f_ functions manager
	*/
	public void init(java.lang.String title, MyDialogs dg, MyFunctions f_)
	{
		settings = dg;
		table = f_.ptable;
		functions = f_;
		
		double pixelsize = table.getValue("X (nm)", 0)/table.getValue("X (px)", 0);
		table.deleteRow(0);
		settings.magn = pixelsize/settings.viewer_tpixelsize;
		
		int new_width=(int) (settings.viewer_owidth*settings.magn+1);
		int new_height=(int) (settings.viewer_oheight*settings.magn+1);
		
		// create the processor
		if (settings.view_mode == settings.view_modes[3]) // 2D particle intensity (8-bit)
			ip=new ByteProcessor(new_width, new_height);
		else if (settings.view_mode == settings.view_modes[1] || settings.view_mode == settings.view_modes[2]) // 2D histogram or 2D particle intensity (16-bit)
			ip=new ShortProcessor(new_width, new_height);
		else // color
			ip=new ColorProcessor(new_width, new_height);
		
		// create the image
		imp = new ImagePlus(title, ip);
		IJ.run(imp, "Set Scale...", "distance=1 known="+settings.viewer_tpixelsize+" pixel=1 unit=nm");
		
		// load data
		functions.ptable_lock.lock();
		s = table.getColumnAsDoubles(0);
		x = table.getColumnAsDoubles(1);
		y = table.getColumnAsDoubles(2);
		z = table.getColumnAsDoubles(5);
		f = table.getColumnAsDoubles(13);
		cl = table.getColumnAsDoubles(14);
		functions.ptable_lock.unlock();
		
		// load max & min values
		for (int n=0;n<f.length;n++)
		{
			if (f[n]>nframes) nframes=(int) f[n];
			if (s[n]>max) max=(int) Math.round(s[n]);
			if (s[n]<min) min=(int) Math.round(s[n]);
			if (z[n]>maxZ) maxZ=(int) Math.round(z[n]);
			if (z[n]<minZ) minZ=(int) Math.round(z[n]);
			if (f[n]>nframes) nframes=(int) f[n];
		}
		
		if (dg.viewer_mergeabove!=0) maxZ=dg.viewer_mergeabove;
		if (dg.viewer_mergebellow!=0) minZ=dg.viewer_mergebellow;
		//max = max/2;
		if (settings.view_mode == settings.view_modes[0]) // color
			calculateColorBar();
	}
	
	/**
	 * the initialize function. here we read the settings from the user settings. and we got the particle table we need. Also we should reset the variables 
	 * @param func
	 */
	public void init(MyFunctions func){
		table = func.ptable;
		functions = func;
		
		position = 0;
		max=0;
		min=9999999;
		maxZ=10;
		minZ=-10;
		//max = max/2;
	}
	
	/**
	 * load the reconstruction settings from our user setting file
	 */
	public void loadSettings(){
		settings.view = true;
		settings.view_modes[0]="3D color";
		settings.view_modes[1]="2D histogram";
		settings.view_modes[2]="2D particle intensity (16-bit)";
		settings.view_modes[3]="2D particle intensity (8-bit)";
		settings.view_modes[4]="2D multicolor";
		settings.view_modes[5]="custom colors";

		settings.viewer_tpixelsize = 1;
		settings.viewer_owidth = 128;
		settings.viewer_oheight = 128;
		settings.view_mode = "3D color";

		settings.viewer_doConvolve = true;
		settings.viewer_do3d = false;
		settings.viewer_doMovie = false;
		settings.viewer_fwhm = 40;

		settings.viewer_zstep = 50;
		settings.viewer_mergeabove = 400;
		settings.viewer_mergebellow = -400;

		settings.viewer_update = 10;
		settings.viewer_accumulate = 100;
		
		settings.magn = 1.0;
	}
	
	/** Class initialize function used on the online rendering mode.
	 * @param title name for the rendering window
	 * @param width original image width
	 * @param height original image height
	 * @param dg dialog manager
	 * @param f functions manager
	*/
	public void init(java.lang.String title, int width, int height, MyDialogs dg, MyFunctions f)
	{
		settings = dg;
		table = f.ptable;
		functions = f;
		
		int new_width=(int) (width*settings.magn+1);
		int new_height=(int) (height*settings.magn+1);
		
		if (settings.view)
		{
			ip=new ColorProcessor(new_width, new_height);
			//calculateColorBar();
			imp = new ImagePlus(title, ip);
			IJ.run(imp, "Set Scale...", "distance=1 known="+settings.pixelsize/settings.magn+" pixel=1 unit=nm");
//			imp.show();//we will not show the
		}
	}
	
	/** Main reconstruction drawing function used by the "Reconstruct Dataset" plugin.
	 * @param fstart show only particle after this frame
	 * @param fstop show only particle before this frame
	 * @param zstart show only particles bellow this z-position
	 * @param zstop show only particles above this z-position
	*/
	public void draw(int fstart, int fstop, double zstart, double zstop)
	{		

		int type = imp.getBitDepth();		
		int xmag, ymag;
		double old_v, new_v = 0;
		int [] old_rgb = new int [3];
		int [] new_rgb = new int [3];

		int satv;

		for (int n=0;n<f.length;n++)
		{
			if (f[n]>=fstart && f[n]<=fstop)
			{
				if (z[n]>=zstart && z[n]<=zstop)
				{
					xmag=(int) Math.round(x[n]*settings.magn);
					ymag=(int) Math.round(y[n]*settings.magn);
					xmag=(xmag>=ip.getWidth())?ip.getWidth()-1:xmag;
					xmag=(xmag<0)?0:xmag;
					ymag=(ymag>=ip.getHeight())?ip.getHeight()-1:ymag;
					ymag=(ymag<0)?0:ymag;
					
					if (settings.view_mode==settings.view_modes[0]) // color
					{
						old_rgb = ip.getPixel(xmag, ymag, old_rgb);
						new_rgb = calculateColor(s[n], z[n]);
						new_rgb[0]+=old_rgb[0];
						new_rgb[1]+=old_rgb[1];
						new_rgb[2]+=old_rgb[2];
						new_rgb[0]=(new_rgb[0]>255)?255:new_rgb[0];
						new_rgb[1]=(new_rgb[1]>255)?255:new_rgb[1];
						new_rgb[2]=(new_rgb[2]>255)?255:new_rgb[2];
						ip.putPixel(xmag, ymag, new_rgb);
					}
					else if (settings.view_mode==settings.view_modes[1]) // 2D histogram
					{
						old_v=ip.get(xmag, ymag);
						new_v=old_v+1;
						ip.set(xmag, ymag, (int) new_v);
					}
					else if (settings.view_mode==settings.view_modes[2]) //2D particle intensity (16-bit)
					{
						old_v=ip.get(xmag, ymag);
						new_v=s[n]+old_v;
						ip.set(xmag, ymag, (int) new_v);
					}
					else if (settings.view_mode.equals(settings.view_modes[3])) // 2D particle intensity (8-bit)
					{
						satv = 255;
						old_v=ip.get(xmag, ymag);
						new_v=((s[n]-min)/(max-min))*satv;
						//if (old_v<new_v)
						//	ip.set(xmag, ymag, (int) Math.round(new_v));
						new_v = Math.round(new_v+old_v);
						new_v = (new_v>satv)?satv:new_v;
						ip.set(xmag, ymag, (int) new_v);
					}
					else if (settings.view_mode.equals(settings.view_modes[4])) // 2D multicolor
					{
						old_rgb = ip.getPixel(xmag, ymag, old_rgb);

					  if (cl[n] == 0) // spring green
						{
							new_rgb[0] = 0;
							new_rgb[1] = 80;
							new_rgb[2] = 0;
						}

  					else if (cl[n] == 1) // red
						{
							new_rgb[0] = 80;
							new_rgb[1] = 0;
							new_rgb[2] = 0;
						}
						
						else if (cl[n] == 2) // blue
						{
							new_rgb[0] = 30;
							new_rgb[1] = 30;
							new_rgb[2] = 75;
						}
						
						else if (cl[n] == 3) // yellow
						{
							new_rgb[0] = 75;
							new_rgb[1] = 75;
							new_rgb[2] = 0;
						}
						
						else if (cl[n] == 4) // white blue
						{
							new_rgb[0] = 0;
							new_rgb[1] = 75;
							new_rgb[2] = 75;
						}
					
						else if (cl[n] == 5) // dark red
						{
							new_rgb[0] = 75;
							new_rgb[1] = 52;
							new_rgb[2] = 52;
						}

						else // light blue
						{
							new_rgb[0] = 0;
							new_rgb[1] = 90;
							new_rgb[2] = 120;
						}

						new_rgb[0]+=old_rgb[0];
						new_rgb[1]+=old_rgb[1];
						new_rgb[2]+=old_rgb[2];
						new_rgb[0]=(new_rgb[0]>255)?255:new_rgb[0];
						new_rgb[1]=(new_rgb[1]>255)?255:new_rgb[1];
						new_rgb[2]=(new_rgb[2]>255)?255:new_rgb[2];
						ip.putPixel(xmag, ymag, new_rgb);
					}

					else // Custom color
					{
						old_rgb = ip.getPixel(xmag, ymag, old_rgb);
						new_rgb[0] = (int)settings.redValue + old_rgb[0];
						new_rgb[1] = (int)settings.greenValue + old_rgb[1];
						new_rgb[2] = (int)settings.blueValue + old_rgb[2];
						new_rgb[0]=(new_rgb[0]>255)?255:new_rgb[0];
						new_rgb[1]=(new_rgb[1]>255)?255:new_rgb[1];
						new_rgb[2]=(new_rgb[2]>255)?255:new_rgb[2];
						ip.putPixel(xmag, ymag, new_rgb);
					}
					
//					if(xmag!=0){
//						System.out.println(xmag+ " " + ymag + "  " + new_v);
//						System.out.println(x[n]+ " " + y[n] + "  " + new_v);
//					}
				}
			}
		}
		if (settings.viewer_doConvolve) gblur.blur(ip, (settings.viewer_fwhm/2.354)/settings.viewer_tpixelsize);
		imp.updateAndDraw();
		//if (imp.isVisible())
		//	IJ.run(imp, "Enhance Contrast", "saturated=0.5");		
	}
	
	/**
	 * show the window alone with a independent window
	 */
	public void showWindow(){
		if(imp != null){
			imp.show();
		}
	}
	
	/** Calculates a color bar to guide users on the position of each particle in Z.*/
	ImagePlus calculateColorBar()
	{
		int border = 20;
		ImageProcessor ipbar = new ColorProcessor(80, 510+border*2);
		ImagePlus impbar = new ImagePlus("Depth coding", ipbar);
		
		
		int [] c = new int [3];
		for (int n=0; n<=510; n++)
		{
			c[0]=(n>=255)?n-255:0;
			c[1]=(n<=255)?n:510-n;
			c[2]=(n<=255)?255-n:0;
			
			for (int m=0; m<10; m++)
				ipbar.putPixel(m,n+border,c);
		}
		
		impbar.updateAndDraw();
		impbar.show();
		
		IJ.runMacro("setFont(\"SansSerif\", 18, \" antialiased\");");
		IJ.run("Colors...", "foreground=white background=black selection=yellow");
		IJ.runMacro("drawString(\"< "+Math.round(minZ)                +"\", "+12+", "+(border+6)+");");
		IJ.runMacro("drawString(\"  "+Math.round(minZ+(maxZ-minZ)*1/6)+"\", "+12+", "+(510*1/6+border+6)+");");
		IJ.runMacro("drawString(\"  "+Math.round(minZ+(maxZ-minZ)*2/6)+"\", "+12+", "+(510*2/6+border+6)+");");
		IJ.runMacro("drawString(\"  "+Math.round(minZ+(maxZ-minZ)*3/6)+"\", "+12+", "+(510*3/6+border+6)+");");
		IJ.runMacro("drawString(\"  "+Math.round(minZ+(maxZ-minZ)*4/6)+"\", "+12+", "+(510*4/6+border+6)+");");
		IJ.runMacro("drawString(\"  "+Math.round(minZ+(maxZ-minZ)*5/6)+"\", "+12+", "+(510*5/6+border+6)+");");
		IJ.runMacro("drawString(\"> "+Math.round(minZ+(maxZ-minZ)*6/6)+"\", "+12+", "+(510*6/6+border+6)+");");
		
		return impbar;
	}
	
	/** Calculates the color for a particle based on its intensity and position in Z.
	 * @param s particle intensity
	 * @param z particle position in z
	 * @return particle color as RGB values
	*/
	int [] calculateColor(double s, double z)
	{
		double vs = ((s-min)/(max-min))*(1+settings.saturation); // allow some saturation
		double vz = ((z-minZ)/(maxZ-minZ));
		int [] c = new int [3];
		
		if (vz>1) vz=1;
		else if (vz<0) vz=0;
		double vz_=255*vz*2;
		
		c[0] = (int) Math.round(((vz>=0.5)?(vz_-255):0)*vs); //R
		c[1] = (int) Math.round(((vz<=0.5)?vz_:(510-vz_))*vs); //G
		c[2] = (int) Math.round(((vz<=0.5)?(255-vz_):0)*vs); //B
		
		c[0] = (c[0]>255)?255:c[0];
		c[1] = (c[1]>255)?255:c[1];
		c[2] = (c[2]>255)?255:c[2];
		
		return c;
	}

	/**
	 * redraw all the points in the particle table
	 */
	public void refresh(){
//		IJ.log("in reconstruction viewer refresh()");
		if(table == null){
//			IJ.log("in reconstruction viewer refresh() table = null");
			table = Analyzer.getResultsTable();
		}
		try{
			double pixelsize = table.getValue("X (nm)", 0)/table.getValue("X (px)", 0);
			settings.magn = pixelsize/settings.viewer_tpixelsize;
//			IJ.log("in reconstruction viewer setting magn" + settings.magn);
		}catch(Exception e){
			IJ.log(e.getMessage());
		}
		
		int new_width=(int) (settings.viewer_owidth*settings.magn+1);
		int new_height=(int) (settings.viewer_oheight*settings.magn+1);
		
		// create the processor
		if (settings.view_mode == settings.view_modes[3]) // 2D particle intensity (8-bit)
			ip=new ByteProcessor(new_width, new_height);
		else if (settings.view_mode == settings.view_modes[1] || settings.view_mode == settings.view_modes[2]) // 2D histogram or 2D particle intensity (16-bit)
			ip=new ShortProcessor(new_width, new_height);
		else // color
			ip=new ColorProcessor(new_width, new_height);
		
		// create the image
		imp = new ImagePlus("", ip);
		position = 0;//set the position to the first point
		update();//set the 
	}
	
	/** Updates the reconstruction viewer with the lattest acquired particles. */
	public void update()
	{
//		IJ.log("in reconstruction viewer uopdate()");
		if (!settings.view) return;
		if(table ==  null) {
			table =  Analyzer.getResultsTable();
		}
		int new_p=table.getCounter();
//		IJ.log("in reconstruction viewer update() " + table.getCounter());
		if (new_p==0 || new_p==position) return;
		update(position, new_p-1);//if we call this like this update(0,new_p-1),then we means paint all the points
		position = new_p;
		canvas.drawImage(imp.getImage());
//		imp.show();
	}
	

	/** Updates the reconstruction viewer by showing the new particles found between
	 * the given indices.
	 * @param start first particle to be updated from the table
	 * @param stop last particle to be updated from the table
	*/
	void update(int start, int stop)
	{
//		IJ.log("in reconstruction viewer uopdate(int,int)");
		// updated May 5th - instead of loading the full columns as arrays, we only grab the needed values, should increase processing speed
//		if (!settings.view) return;
		if(table == null ){
			table = Analyzer.getResultsTable();
		}
		if(functions == null){
			functions = new MyFunctions();
		}
		int nresults = table.getCounter();
		start=(start<0)?0:start;
		stop=(stop>nresults)?(nresults-1):stop;
		synchronized(table){
		s = new double [stop-start+1];
		x = new double [stop-start+1];
		y = new double [stop-start+1];
		z = new double [stop-start+1];
		cl = new double [stop-start+1];
		}
		int index;
//		functions.ptable_lock.lock();
		for (int n=start; n<=stop; n++)
		{
			index = n-start;
			s[index] = table.getValueAsDouble(0, n);
			x[index] = table.getValueAsDouble(1, n);
			y[index] = table.getValueAsDouble(2, n);
			z[index] = table.getValueAsDouble(5, n);
			cl[index] = table.getValueAsDouble(14, n);
		}
//		functions.ptable_lock.unlock();
		boolean newMax=false;
		boolean newMin=false;
		boolean newMaxZ=false;
		boolean newMinZ=false;
		
		// check if there is a new max/min value
		for (int n=0;n<=(stop-start);n++)
		{
			if (s[n]>max)
			{
				newMax=true;
				max=(int) Math.round(s[n]);
			}
			if (s[n]<min)
			{
				newMin=true;
				min=(int) Math.round(s[n]);
			}
			if (z[n]>maxZ)
			{
				newMaxZ=true;
				maxZ=z[n];
			}
			if (z[n]<minZ)
			{
				newMinZ=true;
				minZ=z[n];
			}
			
		}
		
//		if (newMax || newMinZ || newMaxZ || newMin)
//		{
//			clear();
//			update(0, stop); // if a new max/min value is found we need to reupdate the full image
//			return;
//		}
		
		int v, xmag, ymag;
		int [] old_rgb = new int [3];
		int [] new_rgb = new int [3];
		for (int n=0;n<=(stop-start);n++)
		{
			xmag=(int) Math.round(x[n]*settings.magn);
			ymag=(int) Math.round(y[n]*settings.magn);
			ip.getPixel(xmag, ymag, old_rgb);
			//Below is for multicolor only
			if(settings.Multicolor){
				if (cl[n] == 1)//multicolor display
				{
					new_rgb = calculateColor(s[n], 0);//green
				}
				else if(cl[n] == 2)
				{
					new_rgb = calculateColor(s[n], maxZ);//red
				}else {
					new_rgb = calculateColor(s[n], maxZ/2);//red
				}
			}else{
				new_rgb = calculateColor(s[n], z[n]);
			}
			if ((old_rgb[0]+old_rgb[1]+old_rgb[2])<(new_rgb[0]+new_rgb[1]+new_rgb[2]))
				ip.putPixel(xmag, ymag, new_rgb);
		}
//		IJ.log("in reconstruction viewer uopdate(int,int)  imp.update");
		imp.updateAndDraw();
	}
	
	/** Updates the reconstruction viewer by showing particles found between
	 * fstart and fstop.
	 * @param fstart start position of the frame range
	 * @param fstop stop position of the frame range
	*/
	public void updateShort(int fstart, int fstop)
	{
		if (!settings.view) return;
		clear();
		functions.ptable_lock.lock();
		s = table.getColumnAsDoubles(0);
		x = table.getColumnAsDoubles(1);
		y = table.getColumnAsDoubles(2);
		z = table.getColumnAsDoubles(5);
		f = table.getColumnAsDoubles(13);
		cl = table.getColumnAsDoubles(14);
		functions.ptable_lock.unlock();
		
		int xmag, ymag;
		int [] old_rgb = new int [3];
		int [] new_rgb = new int [3];
		for (int n=0;n<f.length;n++)
		{
			if (f[n]>=fstart && f[n]<=fstop)
			{
				xmag=(int) Math.round(x[n]*settings.magn);
				ymag=(int) Math.round(y[n]*settings.magn);
				ip.getPixel(xmag, ymag, old_rgb);
				new_rgb = calculateColor(s[n], z[n]);
				if ((old_rgb[0]+old_rgb[1]+old_rgb[2])<(new_rgb[0]+new_rgb[1]+new_rgb[2]))
					ip.putPixel(xmag, ymag, new_rgb);
			}
		}
		imp.updateAndDraw();
	}
	
	/** Cleans the reconstruction viewer image. */
	public void clear()
	{
		if (!settings.view) return;
		for (int i=0;i<ip.getWidth();i++)
			for (int j=0;j<ip.getHeight();j++)
				ip.set(i, j, 0);
		position=0;
	}

	/**
	 * response the button click
	 */
	@Override
	public void actionPerformed(ActionEvent arg0) {
		JButton tmp = (JButton) arg0.getSource();
		if(tmp.equals(btnSettings)){
			ResultSettingWin.getInstance(settings).show();
		}
	}

	@Override
	public void componentHidden(ComponentEvent arg0) {
		
	}

	@Override
	public void componentMoved(ComponentEvent arg0) {
		
	}

	@Override
	public void componentResized(ComponentEvent arg0) {
		int height = this.getHeight();
		int width = this.getWidth();
//		System.out.println(width + "  o " + height);
		northPanel.setBounds(0, 0, width, 30);
		southPanel.setBounds(0, height-60, width, 60);
		height = height - 90;
		int gapH = 0,gapV = 0;
		if(width>height){
			gapH = width - height;
			centerPanel.setBounds(gapH/2, 30, width-gapH, height);
		}else{
			gapV = height - width;
			centerPanel.setBounds(0, 30+gapV/2, width, height-gapV);
		}		
		canvas.repaint();
		
	}

	@Override
	public void componentShown(ComponentEvent arg0) {
		// TODO Auto-generated method stub
		
	}
}
